package cn.cxyxj.study.webflux03.filter;

import cn.cxyxj.study.webflux03.constant.Constant;
import cn.cxyxj.study.webflux03.constant.RedisKey;
import cn.cxyxj.study.webflux03.entity.SysUserDetails;
import cn.cxyxj.study.webflux03.utils.JWTUtil;
import cn.cxyxj.study.webflux03.utils.RedisUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.util.Objects;

/**
 * 验证 token 是否有效
 * @author cxyxj
 */
public class JwtTokenAuthenticationFilter implements WebFilter {

    private Logger logger = LoggerFactory.getLogger(JwtTokenAuthenticationFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
        //请求不携带 Authorization 请求头
        ServerHttpRequest request = serverWebExchange.getRequest();
        RequestPath url = request.getPath();
        logger.info("url:{}", url);
        String token = request.getHeaders().getFirst(Constant.AUTHORIZATION);
        if (StringUtils.hasLength(token)) {
            JWT jwt = JWTUtil.parseJWT(token);
            Object jti = jwt.getPayload(JWTUtil.JTI);
            SysUserDetails userDetail = RedisUtil.get(String.format(RedisKey.TOKEN, jti), SysUserDetails.class);
            //该 token 在 redis 中不存在(已经失效)
            if (Objects.isNull(userDetail)) {
                    ServerHttpResponse response = serverWebExchange.getResponse();
                    response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
                    response.setStatusCode(HttpStatus.UNAUTHORIZED);
                    String body = JSONUtil.toJsonStr("Token 失效，请重新登录");
                    DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
                    return response.writeWith(Mono.just(buffer));
            }else {
                // 在redis中获取用户信息
                // 不管是用户名密码登录的token 还是邮箱登录的 token，都构建已通过认证的 UsernamePasswordAuthenticationToken 对象
                // 两者登录形式不一样，存储的 userDetail 内容是一致的。
                // 为什么需要将认证对象放入到上下文中？后续的授权流程需要使用到认证对象，它就是从上下文中拿的
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetail, null,userDetail.getAuthorities());
                return webFilterChain.filter(serverWebExchange).contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
            }
        } else {
            // 没有 token 则相当于匿名访问，直接放行，毕竟某些接口是不需要登录就能访问的
            // 比如：登录、注册、忘记密码
            return webFilterChain.filter(serverWebExchange);
        }
    }

}
